/*
 * @author Mingjin Wu, Fei Chen
 * Display 2008 global torch relay schedule and routes on Google maps.
 * Written by Mingjin Wu first, continued by Fei Chen
 */

//Constants

/**
 * Torch icon url.
 */
Torch.URL_ICON_T = Torch.URL + "icons/olympic-torch-24.png";

/**
 * Gray dot icon url.
 */
Torch.URL_ICON_G = Torch.URL + "icons/dot-gray-transparent.png";

/**
 * Red dot icon url.
 */
Torch.URL_ICON_R = Torch.URL + "icons/dot-red-transparent.png";

/**
 * World city type.
 */
Torch.TYPE_WDCT = "w";

/**
 * China province type.
 */
Torch.TYPE_CNPV = "cp";

/**
 * China city type.
 */
Torch.TYPE_CNCT = "c";

/**
 * Torch has passed status.
 */
Torch.STATUS_Y = 1;

/**
 * Torch still not pass status.
 */
Torch.STATUS_N = 2;

/**
 * World region.
 */
Torch.REGION_WD = 1;

/**
 * China region.
 */
Torch.REGION_CN = 2;

/**
 * Event from Map.
 */
Torch.EVENT_M = 1;

/**
 * Event from Panel.
 */
Torch.EVENT_P = 2;

/**
 * Scroll up.
 */
Torch.SCROLL_U = 1;

/**
 * Scroll down.
 */
Torch.SCROLL_D = 2;

/**
 * Player traverse delay.
 */
Torch.PLAYER_DELAY = 5000;

/**
 * Gray color used for polyline.
 */
Torch.COLOR_G = "#333333";

/**
 * Red color used for polyline.
 */
Torch.COLOR_R = "#ff0000";

/**
 * The center coordinate of the world.
 */
Torch.CENTER_WD = new GLatLng(37.633300, 21.616759);

/**
 * The center cordinate of China.
 */
Torch.CENTER_CN = new GLatLng(34.264915, 108.954401);

/**
 * The default map level of world view.
 */
Torch.LEVEL_WD_DF = 2;

/**
 * The default map level of China view.
 */
Torch.LEVEL_CN_DF = 4;

/**
 * The default map level of China province view.
 */
Torch.LEVEL_PV_DF = 8;

/**
 * The map level range to show the overlays of the world.
 */
Torch.LEVEL_WD = [1, 3];

/**
 * The map level range to show the overlays of China.
 */
Torch.LEVEL_CN = [4, 17];

/**
 * The map level range to show the overlays of China province.
 */
Torch.LEVEL_CN_PV = [4, 5];

/**
 * The map level range to show the overlays of China city.
 */
Torch.LEVEL_CN_CT = [6, 17];

/**
 * The range flag of the world map level.
 */
Torch.RANGE_WD = 1;

/**
 * The range flag of the world map level.
 */
Torch.RANGE_CN = 2;

/**
 * The range flag of the world map level.
 */
Torch.RANGE_CN_PV = 3;

/**
 * The range flag of the world map level.
 */
Torch.RANGE_CN_CT = 4;

// Global variables.
Torch.panel = new TPanel();
Torch.map = new TMap();

/**
 * World city list.
 */
Torch.wdctList = null;

/**
 * China city list.
 */
Torch.cnctList = null;

/**
 * China province list.
 */
Torch.cnpvList = null;

/**
 * Torch infomation list.
 */
Torch.infoList = null;

/**
 * Torch video info list.
 */
Torch.videoList = null;

/**
 * Default world city.
 */
Torch.wCity = null;

/**
 * Default china city.
 */
Torch.cCity = null;

/**
 * Current torch city.
 */
Torch.tCity = null;

/**
 * Current region.
 */
Torch.curRegion = -1;

/**
 * Current selected city.
 */
Torch.curCity = null;

/**
 * Player play interval hook.
 */
Torch.playId = null;

/**
 * Scroll bar auto acroll hook.
 */
Torch.scrollId = null;

/**
 *
 */
Torch.pr = ["http://mw2.google.com/mw-panoramio/photos/medium/",
            "http://static.panoramio.com/photos/original/",
            "http://www.panoramio.com/photos/original/"];

Torch.tvU = "http://huoju.cctv.com/";
Torch.rtU = Torch.tvU + "route.php?cityid=";
Torch.voU = Torch.tvU + "video.php?cityid=";
Torch.amU = Torch.tvU + "album.php?cityid=";

Torch.pa = "um=1&ie=UTF-8&sa=N&tab=iw&sourceid=tr_map&q=";

Torch.pgU = "http://www.google.cn/search?";
Torch.imgU = "http://images.google.cn/images?";
Torch.videoU = "http://video.google.cn/videosearch?";
Torch.lU = "http://shenghuo.google.cn/shenghuo/search?a_n1=%E5%9F%8E%E5%B8%82&a_y1=1&a_o1=0&a_v1=";

// Utility functions.
/**
 * Find the html element by id.
 */
Torch.elem = function(id) {
  return document.getElementById(id);
}

/**
 * Find the city object from the corresponding list by id.
 */
Torch.city = function(id) {
  if(!id)
    return null;
  var r = id.match(/(\D+)(\d+)/);
  var idx = parseInt(r[2]);
  switch (r[1]) {
    case Torch.TYPE_WDCT :
      return Torch.wdctList[idx];
    case Torch.TYPE_CNPV :
      return Torch.cnpvList[idx];
    case Torch.TYPE_CNCT :
      return Torch.cnctList[idx];
    default :
      return null;
  }
}

/**
 * Find the city list by region code. Don't include province list;
 */
Torch.list = function(region) {
  return (region == Torch.REGION_WD) ? Torch.wdctList : Torch.cnctList;
}

/**
 * Get city id by its type and index.
 */
Torch.cityId = function(type, idx) {
  return type + idx;
}

/**
 * The player function.
 */
Torch.play = function() {
  var city = Torch.curCity;
  if (Torch.curCity.id.search(/^cp/) == 0) {
    var el = Torch.elem(Torch.curCity.id).nextSibling.firstChild;
    city = Torch.curCity = Torch.cnctList[parseInt(el.id.match(/\d+/))];
  }
  Torch.map.setCity(city);
  Torch.panel.setCity(city);

  Torch.playId = setInterval("Torch.toNext(0);", Torch.PLAYER_DELAY);
  Torch.elem("playButton").style.display = "none";
  Torch.elem("pauseButton").style.display = "block";
}

/**
 * The player function.
 */
Torch.stop = function() {
  clearInterval(Torch.playId);
  Torch.playId == null;
}

/**
 * The player function.
 */
Torch.pause = function() {
  Torch.stop();
  Torch.elem("playButton").style.display = "block";
  Torch.elem("pauseButton").style.display = "none";
}

/**
 * The player function.
 */
Torch.previous = function() {
  Torch.pause();
  Torch.toNext(1);
}

/**
 * The player function.
 */
Torch.next = function() {
  Torch.pause();
  Torch.toNext(0);
}

/**
 * The player help function.
 *
 * @param {int}
 *   isAscend Flag to determine which direction player should go.
 *   1:ascend; 0:descend.
 */
Torch.toNext = function(isAscend) {
  var list = Torch.list(Torch.curRegion);
  var idx, n;
  if (Torch.curCity.id.search(/^cp/) == 0) {
    var el = Torch.elem(Torch.curCity.id).nextSibling.firstChild;
    idx = parseInt(el.id.match(/\d+/));
    n = isAscend ? list[idx - 1] : list[idx];
  } else {
    idx = parseInt(Torch.curCity.id.match(/\d+/));
    n = isAscend ? list[idx - 1] : list[idx + 1];
  }
  if (n) {
    Torch.curCity = n;
    Torch.map.setCity(n);
    Torch.panel.setCity(n);
  }
}

/**
 * Junction point of setting city betweent map and panel.
 * @param {City} city New city.
 * @param {int} eventSrc Event source.
 */
Torch.setCity = function(city, eventSrc) {
  Torch.curCity = city;
  if (eventSrc == Torch.EVENT_P) {
    Torch.map.setCity(city);
  } else if (eventSrc == Torch.EVENT_M) {
    Torch.panel.setCity(city);
  }
}

/**
 * Junction point of setting region betweent map and panel.
 * @param {int} region
 * @param {int} whereComeFrom Indicate who change city initially.
 */
Torch.setRegion = function(region, eventSrc) {
  if(Torch.playId){
    Torch.pause();
  }
  Torch.curRegion = region;
  if (!eventSrc) {
    Torch.map.setRegion(region);
    Torch.panel.setRegion(region);
  } else if (eventSrc == Torch.EVENT_P) {
    Torch.map.setRegion(region);
  } else if (eventSrc == Torch.EVENT_M) {
    Torch.panel.setRegion(region);
  }
  Torch.curCity = (region == Torch.REGION_WD) ? Torch.wCity : Torch.cCity;
}

/**
 * Transform date String to locale string.
 * @param {String} date
 */
Torch.toLocaleDateCN = function(date) {
  return date.replace(/(\d{2})(\d{2})/g,"$1月$2日");
}

Torch.initTorch = function(){
  Torch.elem("worldLink").innerHTML = Torch.worldL;
  Torch.elem("chinaLink").innerHTML = Torch.chinaL;
  Torch.pa = "hl="+Torch.lang+"&" + Torch.pa;
  if (Torch.lang != "zh-CN") {
    Torch.pgU = "http://www.google.com/search?";
    Torch.imgU = "http://images.google.com/images?";
    Torch.newsU = "http://news.google.com/news?";
    Torch.videoU = "http://video.google.com/videosearch?";
  }
}

/**
 * Scroll the city list
 * @param dir The scroll direction.
 */
Torch.scroll = function(dir) {
  var top = (Torch.curRegion == Torch.REGION_WD)
            ? Torch.elem("worldList") : Torch.elem("chinaList");
  var elH = top.firstChild.offsetHeight;
  if (dir == Torch.SCROLL_U) {
    top.scrollTop = top.scrollTop - elH;
  } else if (dir == Torch.SCROLL_D) {
    top.scrollTop = top.scrollTop + elH;
  }
}

/**
 * Start auto scroll.
 * @param dir The scroll direction.
 */
Torch.startScroll = function(dir) {
  Torch.scroll(dir);
  Torch.scrollId = setInterval("Torch.scroll(" + dir + ");", 500);
}

/**
 * Stop auto scroll.
 */
Torch.stopScroll = function() {
  clearInterval(Torch.scrollId);
  Torch.scrollId == null;
}

/**
 * The start point of the whole.
 */
Torch.start = function() {
  var now = getTodayStr();
  var keyD = '0503';
  var sD = '0324';
  var eD = '0808';
  Torch.curRegion = (now <= keyD) ? Torch.REGION_WD : Torch.REGION_CN;
  _IG_FetchContent(Torch.URL_INFO, function(data){
    Torch.infoList = eval(data);
  });
  _IG_FetchContent(Torch.URL_VIDEO, function(data){
    Torch.videoList = eval(data);
  });
  _IG_FetchContent(Torch.URL_DATA, function(data) {
    var dataList = eval(data);
    Torch.wdctList = dataList[0].wdct;
    Torch.cnpvList = dataList[1].cnpv;
    Torch.cnctList = dataList[2].cnct;

    if (Torch.curRegion == Torch.REGION_WD) { // Display world view by default
      if (now < sD) {
        Torch.curCity = Torch.wCity = Torch.wdctList[0];
        importCity(Torch.wdctList, 0, Torch.wdctList.length - 1,
                   Torch.STATUS_N, Torch.TYPE_WDCT);
      } else {
        var idx = findTorchCity(Torch.wdctList, now);
        Torch.curCity = Torch.wCity = Torch.tCity  = Torch.wdctList[idx];
        importCity(Torch.wdctList, 0, idx, Torch.STATUS_Y, Torch.TYPE_WDCT);
        importCity(Torch.wdctList, idx + 1, Torch.wdctList.length - 1,
                   Torch.STATUS_N, Torch.TYPE_WDCT);
      }
      Torch.cCity = Torch.cnctList[0];
      importCity(Torch.cnpvList, 0, Torch.cnpvList.length - 1,
                 Torch.STATUS_N, Torch.TYPE_CNPV);
      importCity(Torch.cnctList, 0, Torch.cnctList.length - 1,
                 Torch.STATUS_N, Torch.TYPE_CNCT);
    } else { // Display China view by default, same logic as above
      Torch.wCity = Torch.wdctList[0];
      importCity(Torch.wdctList, 0, Torch.wdctList.length - 1,
                 Torch.STATUS_Y, Torch.TYPE_WDCT);
      if (now > eD) {
        Torch.curCity = Torch.cCity = Torch.cnctList[0];
        importCity(Torch.cnpvList, 0, Torch.cnpvList.length - 1,
                   Torch.STATUS_Y, Torch.TYPE_CNPV);
        importCity(Torch.cnctList, 0, Torch.cnctList.length - 1,
                   Torch.STATUS_Y, Torch.TYPE_CNCT);
      } else {
        var idx = findTorchCity(Torch.cnctList, now);
        Torch.curCity = Torch.cCity = Torch.tCity = Torch.cnctList[idx];
        var pIdx = parseInt(Torch.tCity.p);
        importCity(Torch.cnpvList, 0, pIdx, Torch.STATUS_Y, Torch.TYPE_CNPV);
        importCity(Torch.cnpvList, pIdx + 1, Torch.cnpvList.length - 1,
                   Torch.STATUS_N, Torch.TYPE_CNPV);
        importCity(Torch.cnctList, 0, idx, Torch.STATUS_Y, Torch.TYPE_CNCT);
        importCity(Torch.cnctList, idx + 1, Torch.cnctList.length - 1,
                   Torch.STATUS_N, Torch.TYPE_CNCT);
      }
    }
    Torch.panel.init(Torch.curRegion);
    Torch.map.init(Torch.curRegion);
  });

  /**
   * Private function of Torch.start().
   * Add cities into panel and map.
   * @param list The city list.
   * @param sIdx Start index in the list to import.
   * @param eIdx End index in the list to import.
   * @param status All import cities's status.
   * @param type All import cities's type.
   */
  function importCity(list, sIdx, eIdx, status, type) {
    Torch.panel.importCity(list, sIdx, eIdx, status, type);
    Torch.map.importCity(list, sIdx, eIdx, status, type);
  }

  /*
   * Private function of Torch.start().
   * Get current date string as format: mmdd.
   */
  function getTodayStr(){
    var now = new Date();
    var m = now.getMonth() + 1; // Function getMonth() is start from 0.
    var d = now.getDate();
    return (m < 10 ? "0" + m : m) + (d < 10 ? "0" + d : d);
  }

  /**
   * Private function of Torch.start().
   * Find the city where the torch should be currently.
   * @param {Array} cityList Array of cities.
   * @param {Date} now Current date object.
   *
   * @return {int} The torch city's index.
   */
  function findTorchCity(list, now) {
    return quickSearch(now, 0, list.length - 1, list);

    /**
     * Private function. Binary search.
     */
    function quickSearch(today, sIdx, eIdx, list) {
      if (sIdx == eIdx) {
        var r = list[sIdx].d.match(/\d{4}/g);
        return (today >= r[0]) ? sIdx : (sIdx - 1);
      }
      var mIdx = Math.floor((eIdx + sIdx) / 2);
      var r = list[mIdx].d.match(/\d{4}/g);
      // The city date format is '****' or '**** - ****'. So
      // r[0] is the numbers of the city date or the date before '-', and
      // r[1] might be exist as the numbers of the date after '-'.
      if (today < r[0] && mIdx > sIdx) {
        return quickSearch(today, sIdx, --mIdx, list);
      } else if ((r[1] && (today > r[1] && mIdx < eIdx))
                 || (!r[1] && (today > r[0] && mIdx < eIdx))) {
        return quickSearch(today, ++mIdx, eIdx, list);
      } else {
        return mIdx;
      }
    }
  }
}

/**
 * A customs class.
 * Used to display and control the torch relay scheduler.
 */
function TPanel() {
  /**
   * Private property.
   */
  var curCityId = null;

  /**
   * Public function of the class.
   * Show the new region in the panel.
   * @param {int} region The new region.
   */
  this.setRegion = function(region) {
    switch (region) {
      case Torch.REGION_WD :
        Torch.elem("worldLink").className = "selected";
        Torch.elem("chinaLink").className = "";
        Torch.elem("worldList").style.display = "block";
        Torch.elem("chinaList").style.display = "none";
        break;
      case Torch.REGION_CN :
        Torch.elem("worldLink").className = "";
        Torch.elem("chinaLink").className = "selected";
        Torch.elem("worldList").style.display = "none";
        Torch.elem("chinaList").style.display = "block";
        break;
    }
    // Restore the current city's style
    var curCity = Torch.elem(curCityId);
    if (curCityId && curCity) {
      var r = curCity.className.split(/\s*selected\s*/);
      curCity.className = r.join(" ");
    }
  }

  /**
   * Private function of the class.
   * Change province node to expand/collapse according to its status.
   * @param {Eelment} elem The province's node.
   */
  function switchProv(elem) {
    if (elem.className.search(/hide/) != -1) {
      expandProv(elem);
    } else if (elem.className.search(/show/) != -1) {
      collapseProv(elem);
    }
  }

  /**
   * Private function of the class.
   * Expand the given province node.
   * @param {Eelment} el The province's node.
   */
  function expandProv(el) {
    el.className = el.className.replace(/hide/, "show");
    el.nextSibling.className = "show-prov";
  }

  /**
   * Private function of the class.
   * Collapse the given province node.
   * @param {Eelment} el The province's node.
   */
  function collapseProv(el) {
    el.className = el.className.replace(/show/, "hide");
    el.nextSibling.className = "hide-prov";
  }

  /**
   * Private function of the changeCity() function.
   * Auto scroll the city list.
   * @param {Element} ctEl
   */
  function scrollToVisible(ctEl) {
    var topPrt = (Torch.curRegion == Torch.REGION_WD)
            ? Torch.elem("worldList") : Torch.elem("chinaList");
    var prtH = topPrt.offsetHeight;
    var elH = ctEl.offsetHeight;
    var srlH = Torch.elem("scrollUp").offsetHeight;
    var pos = getPosition(ctEl)-topPrt.scrollTop;
    if(document.all){//in IE, it ignore the up/down element offsetHeight
      pos += srlH;
      if (pos > prtH) {
        topPrt.scrollTop = ctEl.offsetTop - (prtH - elH);
      } else if (pos < 0) {
        topPrt.scrollTop = ctEl.offsetTop;
      }
    }else{
      if (pos > prtH) {  // Below down scrollBar
        topPrt.scrollTop = ctEl.offsetTop - (prtH - (elH-srlH));
      } else if (pos < srlH) {  // Above up scrollBar
        topPrt.scrollTop = ctEl.offsetTop - srlH;
      }
    }
     /**
     * Private function of scrollToVisible().
     * Calculate the elem's vertical position.
     * @param {Element} elem The target element to get position.
     *
     * @return {int} The target element's vertival scroll height.
     */
    function getPosition(elem) {
      var h = 0;
      for (var e = elem;e && e != topPrt; e = e.offsetParent)
        h += e.offsetTop;
      for (e = elem.parentNode;e && e != topPrt; e = e.parentNode)
        if (e.scrollTop)
          h -= e.scrollTop;
      return h;
    }
  }

  /**
   * Private function of the class.
   * Change the new city to display in the panel.
   * @param {City} city City Object.
   */
  function changeCity(city) {
    // Restore the current city's style
    if ((city.type == Torch.TYPE_WDCT && Torch.curRegion == Torch.REGION_CN)
        || (city.type != Torch.TYPE_WDCT && Torch.curRegion == Torch.REGION_WD)) {
      Torch.setRegion((city.type == Torch.TYPE_WDCT)
                      ? Torch.REGION_WD : Torch.REGION_CN);
    } else {
      var curEl = Torch.elem(curCityId);
      var oldCt = Torch.city(curCityId);
      if (curEl) {
        var r = curEl.className.split(/\s*selected\s*/);
        curEl.className = r.join(" ");
      }
      if (oldCt) {
        if (oldCt.type == Torch.TYPE_CNPV) {
          collapseProv(curEl);
        } else if (oldCt.type == Torch.TYPE_CNCT
                 && oldCt.parentId != city.parentId) {
          collapseProv(Torch.elem(oldCt.parentId));
        }
      }
    }

    // Highlight "new" current city.
    curCityId = city.id;
    curEl = Torch.elem(curCityId);
    var className = curEl.className;
    curEl.className = className + " selected";
    if (city.type == Torch.TYPE_CNPV) {
      switchProv(curEl);
    } else if (city.type == Torch.TYPE_CNCT) {
      var pEl = Torch.elem(city.parentId);
      if (pEl.className.search(/hide/) != -1) {
        expandProv(pEl);
      }
    }
    scrollToVisible(curEl);
  }

  /**
   * Public function of the class.
   * Add cities into the panel.
   * @param {Array} list The list of cities.
   * @param {int} sIdx The start index thar where should be start.
   * @param {int} eIdx The end index that where should be stop.
   * @param {String} status The cities's status that added.
   * @param {String} type The cities's type that added.
   */
  this.importCity = function(list, sIdx, eIdx, status, type) {
    var s = (status == Torch.STATUS_N) ? "noTorch" : "torched";
    for (var i = sIdx; i <= eIdx; i++) {
      var c = list[i];
      c.type = type;
      c.id = Torch.cityId(type, i);
      var node = createCityNode(c, s);
      createNameNode(c.n, node);
      c.n = c.n.replace(/(\S+)(\(\S+\))/,'$1');
      createDateNode(c.d, node);
    }

    /**
     * Private function of importCity().
     * Create the city node in the panel.
     * @param {City} city The city object.
     * @param {String} className The node's className.
     *
     * @return {ELement} The created city node, without name and date children.
     */
    function createCityNode(city, className) {
      var node = document.createElement("p");
      node.className = className;
      node.setAttribute("id", city.id);
      var pNode;
      switch (city.type) {
        case Torch.TYPE_WDCT :
          city.parentId = "worldList";
          pNode = Torch.elem("worldList");
          pNode.appendChild(node);
          break;
        case Torch.TYPE_CNPV :
          city.parentId = "chinaList";
          node.className = node.className + " hide";
          pNode = Torch.elem("chinaList");
          pNode.appendChild(node);
          // Create province wrapper
          var prov = document.createElement("div");
          prov.className = "hide-prov";
          pNode.appendChild(prov);
          break;
        case Torch.TYPE_CNCT :
          city.parentId = "cp" + city.p;
          pNode = Torch.elem(city.parentId).nextSibling; // The "div" tag node
          pNode.appendChild(node);
          break;
      }
      return node;
    }

    /**
     * Private function of importCity().
     * @param {String} name The city name.
     * @param {Element} cityNode The city node that the name should append.
     */
    function createNameNode(name, cityNode) {
      var node = document.createElement("a");
      node.className = "name";
      node.setAttribute("href", "#"); // Make the ':hover' act
      cityNode.appendChild(node);
      node.appendChild(document.createTextNode(name));
      node.innerHTML = node.innerHTML.replace(/(\S+)(\(\S+\))/,'$1<span class="name-desc"> $2 </span>');
      node.onclick = onCityNameClick;
    }

    /**
     * Private function of importCity().
     * @param {String} date The torch relay date passing the city.
     * @param {Element} cityNode The city node that the name should append.
     */
    function createDateNode(date, cityNode) {
      var node = document.createElement("span");
      node.className = "date";
      cityNode.appendChild(node);
      var d = Torch.toLocaleDateCN(date);
      node.appendChild(document.createTextNode(d));
    }

    /**
     * Private function of importCity().
     * The listener of the name node's onClick event.
     */
    function onCityNameClick() {
      var city = Torch.city(this.parentNode.id);
      Torch.setCity(city, Torch.EVENT_P);
      if (curCityId != city.id) {
        changeCity(city);
      } else if(city.type == Torch.TYPE_CNPV){
        switchProv(this.parentNode);
      }
    }
  }

  /**
   * Public function of the class.
   * Change the current city in the panel.
   * @param {City} city The target city to go to.
   */
  this.setCity = function(city) {
    if (city.id != curCityId) {
      changeCity(city);
    }
  }

  /**
   * Public function of the class. Used for TPanel's initiation.
   * @param {int} region The region that panel should be start and display.
   */
  this.init = function(region) {
    this.setRegion(region);
    if (Torch.tCity) {
      this.setCity(Torch.tCity);
    }
  }
}

/**
 * A customs class.
 * Relative to the map in the browser.
 */
function TMap() {
  /**
   * The private property.
   */
  var grayIcon = createIcon(Torch.URL_ICON_G, new GSize(12, 12),
                            new GPoint(6, 6), new GPoint(6, 4));

  /**
   * The private property.
   */
  var redIcon = createIcon(Torch.URL_ICON_R, new GSize(12, 12),
                           new GPoint(6, 6), new GPoint(6, 4));

  /**
   * The private property.
   */
  var tIcon = createIcon(Torch.URL_ICON_T, new GSize(24, 24),
                         new GPoint(4, 20), new GPoint(4, 4));

  /**
   * The private property.
   */
  var gmap = new GMap2();

  /**
   * The private property.
   */
  var olMgr = new OverlayManager(gmap);

  /**
   * The private property.
   */
  var tMarker = null;

  /**
   * The private function of the class.
   */
  function changeCity(city) {
    var p = new GLatLng(city.la, city.ln);
    if (tMarker) {
      gmap.removeOverlay(tMarker);
      tMarker.setPoint(p);
    } else {
      tMarker = new GMarker(p, { icon : tIcon });;
    }
//    gmap.panTo(p);
    gmap.addOverlay(tMarker);
    addMarkerEvent(tMarker, city);
    openInfoWindow(tMarker, city);
  }

  function openInfoWindow(marker, city) {
    var tabs = [];
    if (city.type == Torch.TYPE_CNPV) {  // Province
      tabs.push(new GInfoWindowTab(city.n, getProvHtml(city)));
    } else {
      var index = city.id.match(/\D+(\d+)/)[1];
      var idx = ( city.type == Torch.TYPE_WDCT ) ? index : (parseInt(index) + 23);
      var info, video;
      if(Torch.infoList){
        info = Torch.infoList[idx];
      } else {
        _IG_FetchContent(Torch.URL_INFO, function(data){
          Torch.infoList = eval(data);
          openInfoWindow(marker, city);
        });
        return;
      }
      if(Torch.videoList){
        video = Torch.videoList[idx];
      } else {
        _IG_FetchContent(Torch.URL_VIDEO, function(data){
          Torch.videoList = eval(data);
          openInfoWindow(marker, city);
        });
        return;
      }
      var infoHtml = getInfoHtml(city, info, idx);
      var videoHtml = getVideoHtml(city, video, info, idx);
      var imgHtml = getImgHtml(city, info, idx);
      tabs.push(new GInfoWindowTab(Torch.tabT_p, imgHtml));
      tabs.push(new GInfoWindowTab(Torch.tabT_i, infoHtml));
      tabs.push(new GInfoWindowTab(Torch.tabT_v, videoHtml));
    }
    marker.openInfoWindowTabsHtml(tabs, { disableGoogleLinks : true, maxWidth : 320 , selectedTab : 0 });
  }

  function getProvHtml(city) {
    var html = '<div style="font-size: 12pt; font-weight: bold;">'
               + city.n + '</div>';
    html = '<div style="font-size: 12pt; font-weight: bold;">' + city.n;
    html += '<span style="font-size: 10pt; color: #C0C0C0; margin-left:15px;">'
            + Torch.toLocaleDateCN(city.d) + '</span></div>';
    html += '<div style="margin:5px;font-size:13px;">' + Torch.passL + '</div>';
    html += '<div style="margin:5px; border-top:solid 2px #C0C0C0">';
    for(var i = 0; i < city.c.length ; i++){
      var child = Torch.cnctList[city.c[i]];
      html += '<p style="position:relative; font-size:13px; color: #000;margin:5px;clear:both;">';
      html += child.n + '<span style="position:absolute; right:5px; color: #C0C0C0;">';
      html += Torch.toLocaleDateCN(child.d)+'</span></p>';
    }
    html += '</div>';
    return html;
  }

  function getVideoHtml(city, vdo, info, idx) {
    var id = (vdo.videoID.search(/\*/) != -1) ? '1005_2004_3000_4000_2008_03_26_15_45_39_9554' : vdo.videoID;
    var title = (vdo.videoTitle.search(/\*/) != -1) ? Torch.vT : vdo.videoTitle;
    var html = '<div style="font-size:13px; width:300px;">';
    html += '<p style="color:#666; margin:5px 0;"><span style="font-weight:bold;color:#990033;font-size:1.3em;margin-right:10px;">' + city.n + '</span><p style="margin:-8px 0 4px 0;">' + title + '</p></p>';
    if (document.all) {
      if (id.match(/^\d{4}_\d{4}/)) {
        html += ' <embed flashvars="size=1&id=' + id + '&site=http://sports.cctv.com" ';
        html += '       src="http://sports.cctv.com/playcfg/player.swf" ';
        html += '       type="application/x-shockwave-flash" ';
        html += '       allowfullscreen="true" ';
        html += '       wmode="window" ';
        html += '       allowscriptaccess="always" height="242" width="300"></embed>';
      } else {
        html += ' <embed flashvars="id=' + id + '&amp;site=http://sports.cctv.com&method=http" ';
        html += '       src="http://sports.cctv.com/playcfg/player_new.swf" ';
        html += '       type="application/x-shockwave-flash" ';
        html += '       allowfullscreen="true" ';
        html += '       wmode="window" ';
        html += '       autostart="false"';
        html += '       allowscriptaccess="always" height="242" width="300"></embed>';
      }
    } else {
      if (id.match(/^\d{4}_\d{4}/)) {
        html += '<object height="242" width="300">';
        html += ' <param name="movie" value="http://sports.cctv.com/playcfg/player.swf"></param>';
        html += ' <param name="allowScriptAccess" value="always"></param>';
        html += ' <param name="wmode" value="window"></param>';
        html += ' <param name="flashvars" value="size=1&id=' + id + '.xml&http://sports.cctv.com"></param>';
        html += ' <embed flashvars="size=1&id=' + id + '.xml&site=http://sports.cctv.com" ';
        html += '       src="http://sports.cctv.com/playcfg/player.swf" ';
        html += '       type="application/x-shockwave-flash" ';
        html += '       allowfullscreen="true" ';
        html += '       wmode="window" ';
        html += '       allowscriptaccess="always" height="242" width="300"></embed>';
        html += '</object>';
      } else {
        html += '<object height="242" width="300" style="z-index:200;">';
        html += ' <param name="movie" value="http://sports.cctv.com/playcfg/player_new.swf"></param>';
        html += ' <param name="allowScriptAccess" value="always"></param>';
        html += ' <param name="AutoStart" value="0"></param>';
        html += ' <param name="wmode" value="window"></param>';
        html += ' <param name="flashvars" value="id=' + id + '&amp;site=http://sports.cctv.com&method=http"></param>';
        html += ' <embed flashvars="id=' + id + '&amp;site=http://sports.cctv.com&method=http" ';
        html += '       src="http://sports.cctv.com/playcfg/player_new.swf" ';
        html += '       type="application/x-shockwave-flash" ';
        html += '       allowfullscreen="true" ';
        html += '       wmode="window" ';
        html += '       autostart="false"';
        html += '       allowscriptaccess="always" height="242" width="300"></embed>';
        html += '</object>';
      }
    }
    html += '<p style="font-size:0.9em;color:#666;clear:both;margin:-1px 0 3px 0;">' + Torch.vcpL + '</p>';
    html += '<div style="position:relative;margin:5px 0;">';
    html += ' <span style="position:absolute;left:0;font-weight:bold;color:#990033;">' + Torch.linkL + '</span>';
    html += ' <div style="width:220px;margin-left:68px;">';
    html += '  <p style="margin:3px 0 0 0;"><a href="' + Torch.videoU + Torch.pa + encodeURIComponent(info.name) + '" target="_blank">' + Torch.moreL + info.name + Torch.videoL + '</a></p>';
    html += '  <p style="margin:3px 0 0 0;">' + Torch.tvN + '<a href="' + Torch.voU + idx + '" target="_blank">' + info.name + Torch.vL + '</a></p>';
    html += ' </div>';
    html += '</div></div>';
    return html;
  }

  function getImgHtml(city, info, idx) {
    if(info.picU.search(/@(\d*)@/) != -1){
      info.picU = info.picU.replace(/@(\d*)@/, Torch.pr[info.picU.match(/@(\d*)@/)[1]-1]);
    }
    var html = '<div style="font-size:13px; width:300px;">';
    html += ' <p style="text-align:left; color:#666; margin:5px 0;"><span style="font-weight:bold;color:#990033;font-size:1.3em;margin-right:10px;">' + city.n + '</span>' + info.picT + '</p>';
    html += ' <p style="margin:0;">';
    html += '  <a href="' + info.picU + '" target="_blank" style="margin:0;"><img style="margin:0;height:200px;width:300px;" src="' + info.picU + '"/></a>';
    html += ' </p>';
    html += '  <p style="font-size:0.9em;color:#666;clear:both;margin:-1px 0 3px 0;">' + Torch.pcpL + '</p>';
    html += '<div style="position:relative;margin:5px 0;">';
    html += ' <span style="position:absolute;left:0;font-weight:bold;color:#990033;">' + Torch.linkL + '</span>';
    html += ' <div style="width:220px;margin-left:68px;">';
    html += '   <p style="margin:3px 0 0 0;"><a href="' + Torch.imgU + Torch.pa + encodeURIComponent(info.name) + '" target="_blank">' + Torch.moreL + info.name + Torch.imgL + '</a></p>';
    html += '   <p style="margin:3px 0 0 0;">' + Torch.tvN + '<a href="' + Torch.amU + idx + '" target="_blank">' + info.name + Torch.aL + '</a></p>';
    html += ' </div>';
    html += '</div></div>';
    return html;
  }

  function getInfoHtml(city, info, idx) {
    var html = '<div class="intro" style="font-size:13px; width:300px;">';
    html += '<p style="text-align:left; color:#666; margin:5px 0;"><span style="font-weight:bold;color:#990033;font-size:1.3em;margin-right:10px;">' + city.n + '</span>' + Torch.toLocaleDateCN(city.d) + '</p>';
    html += '<p style="color:#666;margin:3px;position:relative;padding:3px 5px;">' + info.intro + '  </p>';
    html += '<p style="width:100%;text-align:right;margin:0 10px 5px 0;"><span style="padding-left:16px;"><img src="' + Torch.URL + 'icons/olympic-torch-16.gif"/>' + Torch.nextL + '</span>' + info.next + '</p>';
    html += '<div style="position:relative;margin:10px 0;">';
    html += ' <span style="position:absolute;left:0;font-weight:bold;color:#990033;">' + Torch.linkL + '</span>';
    html += ' <div style="width:220px;margin-left:68px;">';
    html += '  <p style="margin:3px 0 0 0;"><a href="' + Torch.pgU + Torch.pa + encodeURIComponent(info.name) + '" target="_blank">' + Torch.moreL + info.name + Torch.pgL + '</a></p>';
    if(city.type == Torch.TYPE_CNCT){
      html += ' <p style="margin:3px 0 0 0;"><a href="' + Torch.lU + encodeURIComponent(info.name) + '" target="_blank">' + info.name + Torch.lL + '</a></p>';
    }
    html += '  <p style="margin:3px 0 0 0;"><a href="' + Torch.tvU + '" target="_blank">' + Torch.tvL + '</a></p>';
    html += '  <p style="margin:3px 0 0 0;"><a href="' + Torch.rtU + idx+'" target="_blank">' + Torch.rL + '</a></p>';
    html += ' </div>';
    html += '</div></div>';
    return html;
  }

  /**
   * Private function of the class.
   * Create GIcon object.
   */
  function createIcon(image, iconSize, iconAnchor, infoWindowAnchor) {
    var icon = new GIcon();
    icon.image = image;
    icon.iconSize = iconSize;
    icon.iconAnchor = iconAnchor;
    icon.infoWindowAnchor = infoWindowAnchor;
    return icon;
  }

  /**
   * Private function of the class.
   * Add marker event listener.
   */
  function addMarkerEvent(marker, city) {
    GEvent.addListener(marker, 'click', function() {
      changeCity(city);
      Torch.setCity(city, Torch.EVENT_M);
    });
  }

  /**
   * Public function of the class.
   * Define relative maker and polyline.
   * NOTE: There is still not add city into map.
   *
   */
  this.importCity = function(list, sIdx, eIdx, status, type) {
    var icon = (status == Torch.STATUS_N) ? grayIcon : redIcon;
    var points = [];
    if (sIdx != 0 && list[sIdx - 1]) {
      points.push(new GLatLng(list[sIdx - 1].la, list[sIdx - 1].ln));
    }
    for (var i = sIdx, c, p, m; i <= eIdx; i++) {
      c = list[i];
      p = new GLatLng(c.la, c.ln);
      m = new GMarker(p, { icon : icon, title : c.n });
      olMgr.addMarker(m, type);
      addMarkerEvent(m, c);
      points.push(p);
    }
    if (type != Torch.TYPE_CNPV && points.length > 0) {
      var color = (status == Torch.STATUS_N) ? Torch.COLOR_G : Torch.COLOR_R;
      for (var i = 1, point, line; point = points[i]; i++) {
        line = new GPolyline([points[i - 1], point], color, 2, 1);
        olMgr.addPolyline(line, type);
      }
    }
  }

  /**
   * Public function of the class.
   * Change the current city in the map.
   * @param {City} city The target city to go to.
   */
  this.setCity = function(city) {
    switch (city.type) {
      case Torch.TYPE_WDCT :
        gmap.setZoom(Torch.LEVEL_WD_DF);
        break;
      case Torch.TYPE_CNPV :
        gmap.setZoom(Torch.LEVEL_CN_DF);
        break;
      case Torch.TYPE_CNCT :
        gmap.setZoom(Torch.LEVEL_PV_DF);
        break;
    }
    changeCity(city);
  }

  /**
   * Public function of the class.
   * To set the map's display region.
   * @param {int} region The region code.
   */
  this.setRegion = function(region) {
    if (tMarker) {
      gmap.removeOverlay(tMarker);
    }
    gmap.closeInfoWindow();
    olMgr.refresh(region);
  }

  /**
   * Public function of the class.
   * TMap initiation.
   * @param {int} region The region that the map should be display.
   */
  this.init = function(region) {
    gmap.clearOverlays();
    this.setRegion(region);
    GEvent.addListener(gmap, "zoomend", function(oldLevel, newLevel) {
      var region = olMgr.getRegionByLevel(newLevel);
      if (region != Torch.curRegion) {
        Torch.setRegion(region, Torch.EVENT_M);
        if (tMarker) {
          gmap.removeOverlay(tMarker);
        }
        gmap.closeInfoWindow();
      }
      olMgr.changeOverlays(oldLevel, newLevel);
    });
    // A hack to show overlays when init
    switch (region) {
      case Torch.REGION_WD :
        olMgr.changeOverlays(Torch.LEVEL_CN_DF, Torch.LEVEL_WD_DF);
        break;
      case Torch.REGION_CN :
        olMgr.changeOverlays(Torch.LEVEL_WD_DF, Torch.LEVEL_CN_DF);
    }

    if (Torch.tCity) {
      Torch.setCity(Torch.tCity);
    }
  }
}

/**
 * To manage the markers and polylines.
 * @param {GMap2} gmap
 */
function OverlayManager(gmap) {
  /**
   * The private property.
   */
  var map = gmap;

  /**
   * The private property.
   * All overlays arrays' first elem store the flag as added or not
   * and the second elem store the flag as showed or not.
   */
  var wdctMarkers = [false/*hasAdded*/, false/*hasShowed*/];

  /**
   * The private property.
   */
  var cnctMarkers = [false, false];

  /**
   * The private property.
   */
  var cnpvMarkers = [false, false];

  /**
   * The private property.
   */
  var wdLines = [false, false];

  /**
   * The private property.
   */
  var cnLines = [false, false];

  /**
   * Show all the overlays in the given list.
   * If the overlays do not added to the map, add them.
   * @param {Array} overlayList The list of overlays.
   */
  function showOverlays(overlayList) {
    if (!overlayList[0]) {
      add(overlayList);
      overlayList[0] = true;
      overlayList[1] = true;
    } else if (!overlayList[1]) {
      show(overlayList);
      overlayList[1] = true;
    }

    /**
     * Private function of showOverlays(), add the overlays into map.
     * @param {Array} overlayList
     */
    function add(overlayList) {
      for (var i = 2, overlay; overlay = overlayList[i]; i++) {
        map.addOverlay(overlay);
      }
    }

    /**
     * Private function of showOverlays(), show the overlays.
     * @param {Array} overlayList
     */
    function show(overlayList) {
      for (var i = 2, overlay; overlay = overlayList[i]; i++) {
        overlay.show();
      }
    }
  }

  /**
   * Private function of the class.
   * Hide all the overlays in the given list, if still not added to the map,
   * do nothing.
   * @param {Array} overlayList
   */
  function hideOverlays(overlayList) {
    if (overlayList[0] && overlayList[1]) {
      hide(overlayList);
      overlayList[1] = false;
    }

    /**
     * Private function of hideOverlays(), hide the overlays.
     * @param {Array} overlayList
     */
    function hide(overlayList) {
      for (var i = 2, overlay; overlay = overlayList[i]; i++) {
        overlay.hide();
      }
    }
  }

  /**
   * Public function of the class.
   * Add marker into the overlayManager.
   * @param {GMarker} marker
   * @param {int} cityType
   */
  this.addMarker = function(marker, cityType) {
    switch (cityType) {
      case Torch.TYPE_WDCT :
        wdctMarkers.push(marker);
        break;
      case Torch.TYPE_CNCT :
        cnctMarkers.push(marker);
        break;
      case Torch.TYPE_CNPV :
        cnpvMarkers.push(marker);
    }
  }

  /**
   * Public function of the class.
   * Add polyline into the overlayManager.
   * @param {GPolyline} polyline
   * @param {int} cityType
   */
  this.addPolyline = function(polyline, cityType) {
    switch (cityType) {
      case Torch.TYPE_WDCT :
        wdLines.push(polyline);
        break;
      case Torch.TYPE_CNCT :
        cnLines.push(polyline);
    }
  }

  /**
   * Public function of the class.
   * Invoked when the region is changed.
   * @param {int} region
   */
  this.refresh = function(region) {
    switch (region) {
      case Torch.REGION_WD :
        map.setCenter(Torch.CENTER_WD, Torch.LEVEL_WD_DF);
        break;
      case Torch.REGION_CN :
        map.setCenter(Torch.CENTER_CN, Torch.LEVEL_CN_DF);
    }
  }

  /**
   * Private function of the class.
   * Show special markers according to the level range.
   * @param {int} levelRange
   */
  function showMarkers(levelRange) {
    switch (levelRange) {
      case Torch.RANGE_CN_PV :
        showOverlays(cnpvMarkers);
        break;
      case Torch.RANGE_CN_CT :
        showOverlays(cnctMarkers);
        break;
      case Torch.RANGE_WD :
        showOverlays(wdctMarkers);
    }
  }

  /**
   * Private function of the class.
   * Hide special markers according to the level range.
   * @param {int} levelRange
   */
  function hideMarkers(levelRange) {
    switch (levelRange) {
      case Torch.RANGE_CN_PV :
        hideOverlays(cnpvMarkers);
        break;
      case Torch.RANGE_CN_CT :
        hideOverlays(cnctMarkers);
        break;
      case Torch.RANGE_WD :
        hideOverlays(wdctMarkers);
    }
  }

  /**
   * Private function of the class.
   * Show special polylines according to the level range.
   * @param {int} levelRange
   */
  function showPolylines(levelRange) {
    switch (levelRange) {
      case Torch.RANGE_WD :
        showOverlays(wdLines);
        break;
      case Torch.RANGE_CN_PV :
      case Torch.RANGE_CN_CT :
        showOverlays(cnLines);
    }
  }

  /**
   * Private function of the class.
   * Hide special polylines according to the level range.
   * @param {Object} levelRange
   */
  function hidePolylines(levelRange) {
    switch (levelRange) {
      case Torch.RANGE_WD :
        hideOverlays(wdLines);
        break;
      case Torch.RANGE_CN_PV :
      case Torch.RANGE_CN_CT :
        hideOverlays(cnLines);
    }
  }

  /**
   * Private function of the class.
   * Get level range according to the given level.
   * @param {int} level The Google map's level.
   */
  function getLevelRange(level) {
    if (Torch.LEVEL_CN_PV[0] <= level
        && level <= Torch.LEVEL_CN_PV[1]) {
      return Torch.RANGE_CN_PV;
    }
    if (Torch.LEVEL_CN_CT[0] <= level
        && level <= Torch.LEVEL_CN_CT[1]) {
      return Torch.RANGE_CN_CT;
    }
    if (Torch.LEVEL_WD[0] <= level && level <= Torch.LEVEL_WD[1]) {
      return Torch.RANGE_WD;
    }
  }

  /**
   * Public function of the class.
   * Get region code according the the special level.
   * @param {int} newLevel The Google map's level.
   */
  this.getRegionByLevel = function(level) {
    if (level >= Torch.LEVEL_CN[0]) {
      return Torch.REGION_CN;
    } else if (level <= Torch.LEVEL_WD[1]) {
      return Torch.REGION_WD;
    }
  }

  /**
   * Public function of the class.
   * Control the overlays' display on the map according to the levels.
   * @param {int} oldLevel The Google Map's level.
   *                       Need to hide overlays on this level.
   * @param {int} newLevel The Google Map's level.
   *                       Need to show overlays on this level.
   */
  this.changeOverlays = function(oldLevel, newLevel) {
    var oldLevelRange = getLevelRange(oldLevel);
    var newLevelRange = getLevelRange(newLevel);
    if (oldLevelRange != newLevelRange) {
      hideMarkers(oldLevelRange);
      hidePolylines(oldLevelRange);
      showMarkers(newLevelRange);
      showPolylines(newLevelRange);
    }
  }
}
